﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace LeksickiAnalizator
{
    /// <summary>
    /// Dio definicije leksičkog analizatora sa programskom logikom
    /// tablice koja definira ponašanje analizatora
    /// </summary>
    public partial class LeksickiAnalizator
    {
        #region DEFINICIJA AUTOMATA
        /// <summary>
        /// Glavna struktura za spremanje informacija o leksičkom analizatoru.
        /// Ključ strukture je string koji prestavlja lijevi kontekst analizatora.
        /// Ovisno o tom kontekstu, vraća se drugi rječnik sa informacijama o 
        /// svim kombinacijama stanja i prijelaza važećih za taj lijevi kontekst. 
        /// Ključ u tom drugom rječniku je oznaka trenutnog stanja (string), a 
        /// vraća se struktura sa prijelazima iz tih stanja, prihvatljivosti stanja, 
        /// argumentima eventualnih akcija te prioritetu pravila. Struktura je 
        /// tipa ove ugnježđene klase Stanje.
        /// </summary>
        private IDictionary<string, IDictionary<string, Stanje>> tablicaPrijelaza;

        /// <summary>
        /// Tablica početnih stanja leksičkog analizatora. 
        /// Ključ je lijevi kontekst, a vrijednost skup početnih stanja
        /// automata koji prepoznaju regularne izraze
        /// </summary>
        private IDictionary<string, ISet<string>> tablicaPocetnihStanja;

        /// <summary>
        /// Početni lijevi kontekst leksičkog analizatora
        /// </summary>
        private string pocetniLijeviKontekst;

        /// <summary>
        /// Predstavlja jedan zapis u tablici prijelaza
        /// </summary>
        public class Stanje
        {
            /// <summary>
            /// Idenitifikator trenutnog stanja. Mora biti jedinstven unutar skupa
            /// stanja koja pripadaju određenom lijevom kontekstu. On je također i 
            /// redom drugi ključ u tablici prijelaza
            /// </summary>
            public string Id { get; set; }

            /// <summary>
            /// Tablica prijelaza stanja. Ključ je ulazni znak, a vrijednost
            /// skup identifikatora stanja u koja se prelazi. Za dohvaćanje prijelaza
            /// trebalo bi koristiti kasnije definiranu metodu DohvatiPrijelaze(char).
            /// Epislon prijelazi su interno spremljeni kao prijelazi znaka '€'
            /// </summary>
            public IDictionary<char, ISet<String>> Prijelazi { get; set; }

            /// <summary>
            /// Označava prihvatljivo stanje. Vrijednost ovog svojstva je true ako je 
            /// stanje prihvaljivo ili false ako nije.
            /// </summary>
            public bool JePrihvatljivo { get; set; }

            /// <summary>
            /// Sprema argumente akcije. Spremljeni su u rječniku kojemu su i ključevi
            /// i vrijednosti koje vraća tipa string. Imena argumenata su:
            ///     - imeTokena (string)
            ///     - noviRedak (true/false)
            ///     - imeStanja (string)
            ///     - naZnak (string)
            /// Budući da su sve vrijednosti spremljene kao tekst, po potrebi ih moramo
            /// parsirati.
            /// </summary>
            public IDictionary<string, string> ArgumentiAkcije { get; set; }

            /// <summary>
            /// Označava prioritet pravila. Manja vrijednost označava ranije pojavljivanje
            /// u definiciji i s time i veći prioritet. 
            /// Ima smisla samo za prihvatljiva stanja.
            /// Kada više regularnih izraza odgovara nekom ulazu, mora se prihvatiti 
            /// onaj većim prioritetom (manja ova vrijednost).
            /// </summary>
            public int Prioritet { get; set; }

            /// <summary>
            /// Dohvaća prijelaze za znak c
            /// </summary>
            /// <param name="c">ulazni znak</param>
            /// <returns>skup stanja u koja se prelazi</returns>
            /// <remarks>epsilon prijelaz je predstavljen prijelazon za
            /// znak €(alt-gr + e na hrvatskoj tipkovnici:-))</remarks>
            public ISet<string> DohvatiPrijelaze(char c)
            {
                if (Prijelazi.ContainsKey(c))
                    return Prijelazi[c];
                else
                    return new HashSet<string>();
            }
        }

        #endregion

        #region KONSTRUKTORI
        /// <summary>
        /// Javni konstruktor razreda. Poziva generiran kod koji
        /// puni tablicu prijelaza
        /// </summary>
        public LeksickiAnalizator()
        {
            Init();
        }

        /// <summary>
        /// Javni konstruktor razreda koji postavlja potrebne tablice
        /// </summary>
        /// <param name="listaUniformnihZnakova">lista uniformnih znakova</param>
        /// <param name="tablicaIzvornihZnakova">tablica izvornih znakova</param>
        public LeksickiAnalizator(ListaUniformnihZnakova listaUniformnihZnakova, TablicaIzvornihZnakova tablicaIzvornihZnakova)
        {
            Init();
            this.listaUniformnihZnakova = listaUniformnihZnakova;
            this.tablicaIzvornihZnakova = tablicaIzvornihZnakova;
        }
        #endregion

        /// <summary>
        /// Lista uniformnih znakova
        /// </summary>
        private ListaUniformnihZnakova listaUniformnihZnakova;

        /// <summary>
        /// Tablica konstanti
        /// </summary>
        private TablicaIzvornihZnakova tablicaIzvornihZnakova;

        private char[] ulazniZnakovi;
        private int pocetakNeprepoznatogDijelaNiza;
        private int pocetakNeprocitanogOstatkaNiza;
        private int indeksKrajaPosljednjegPrihvacenogNiza;
        private string indeksStanjaPosljednjegPrihvacenogNiza;
        private string lijeviKontekst;
        private ISet<string> trenutnaStanja;
        private int brojReda;

        /// <summary>
        /// Metoda kojom se pokreće rad leksičkog analizatora
        /// </summary>
        /// <param name="izvorniKod">tok sa kojeg se može dohvatiti izvorni od</param>
        /// <returns>popis pogrešaka kod leksičke analize</returns>
        public IList<Pogreska> Obradi(StreamReader izvorniKod)
        {
            IList<Pogreska> listaPogreski = new List<Pogreska>();

            ulazniZnakovi = izvorniKod.ReadToEnd().Replace("\r","").ToCharArray();
            pocetakNeprepoznatogDijelaNiza = 0;
            pocetakNeprocitanogOstatkaNiza = 0;
            indeksKrajaPosljednjegPrihvacenogNiza = 0;
            indeksStanjaPosljednjegPrihvacenogNiza = null;
            lijeviKontekst = pocetniLijeviKontekst;
            brojReda = 1;

            trenutnaStanja = epsilonOkruzenje(tablicaPocetnihStanja[lijeviKontekst]);
            
            while (pocetakNeprocitanogOstatkaNiza < ulazniZnakovi.Length)
            {
                char ulazniZnak = ulazniZnakovi[pocetakNeprocitanogOstatkaNiza++];

                trenutnaStanja = NapraviPrijelaz(ulazniZnak);

                ZabiljeziPrihvatljivoStanje();

                if (trenutnaStanja.Count == 0)
                {
                    if (indeksStanjaPosljednjegPrihvacenogNiza != null)
                    {
                        IzvrsiAkciju();
                        indeksStanjaPosljednjegPrihvacenogNiza = null;
                        pocetakNeprepoznatogDijelaNiza = indeksKrajaPosljednjegPrihvacenogNiza + 1;
                        pocetakNeprocitanogOstatkaNiza = pocetakNeprepoznatogDijelaNiza;
                        trenutnaStanja = epsilonOkruzenje(tablicaPocetnihStanja[lijeviKontekst]);
                    }
                    else
                    {
                        OporavakOdPogreske(listaPogreski);
                        indeksStanjaPosljednjegPrihvacenogNiza = null;
                        trenutnaStanja = epsilonOkruzenje(tablicaPocetnihStanja[lijeviKontekst]);
                    }
                }
                
            }

            return listaPogreski;
        }

        /// <summary>
        /// Izvršava zadani oporavak od pogreške
        /// </summary>
        private void OporavakOdPogreske(IList<Pogreska> listaPogreski)
        {
            listaPogreski.Add(new Pogreska()
                {
                    Redak=brojReda,
                    Znak=ulazniZnakovi[pocetakNeprepoznatogDijelaNiza]
                });
            pocetakNeprepoznatogDijelaNiza++;
            pocetakNeprocitanogOstatkaNiza = pocetakNeprepoznatogDijelaNiza;
        }

        /// <summary>
        /// Izvršava akciju leksičkog analizatora
        /// </summary>
        private void IzvrsiAkciju()
        {
            var argumenti = tablicaPrijelaza[lijeviKontekst][indeksStanjaPosljednjegPrihvacenogNiza].ArgumentiAkcije;

            if (argumenti["naZnak"] != "-1")
            {
                indeksKrajaPosljednjegPrihvacenogNiza = pocetakNeprepoznatogDijelaNiza + Convert.ToInt32(argumenti["naZnak"])-1;
            }

            if (argumenti["imeTokena"] != "-")
            {
                UpisUniformnogZnaka(argumenti["imeTokena"]);
            }

            if (Convert.ToBoolean(argumenti["noviRedak"]))
            {
                brojReda++;
            }

            if (argumenti["imeStanja"] != "")
            {
                lijeviKontekst = argumenti["imeStanja"];
            }
        }

        /// <summary>
        /// Radi prijelaz za dani ulazni znak
        /// </summary>
        /// <param name="ulazniZnak">ulazni znak</param>
        /// <returns>skup stanja nakon prijelaza</returns>
        private ISet<string> NapraviPrijelaz(char ulazniZnak)
        {
            ISet<string> buducaStanja = new HashSet<string>();
            foreach (var trenutnoStanje in trenutnaStanja)
            {
                if (tablicaPrijelaza[lijeviKontekst][trenutnoStanje].Prijelazi.ContainsKey(ulazniZnak))
                    buducaStanja.UnionWith(tablicaPrijelaza[lijeviKontekst][trenutnoStanje].Prijelazi[ulazniZnak]);
            }
            trenutnaStanja = epsilonOkruzenje(buducaStanja);
            return trenutnaStanja;
        }

        /// <summary>
        /// Bilježi prihvatljivo stanje najmanjom oznakom prioriteta
        /// </summary>
        private void ZabiljeziPrihvatljivoStanje()
        {
            bool zabiljezenoPrihvatljivoStanje = false;
            foreach (var trenutnoStanje in trenutnaStanja)
            {
                if (tablicaPrijelaza[lijeviKontekst][trenutnoStanje].JePrihvatljivo)
                {
                    if (!zabiljezenoPrihvatljivoStanje)
                    {
                        indeksKrajaPosljednjegPrihvacenogNiza = pocetakNeprocitanogOstatkaNiza - 1;
                        indeksStanjaPosljednjegPrihvacenogNiza = trenutnoStanje;
                        zabiljezenoPrihvatljivoStanje = true;
                    }
                    if (tablicaPrijelaza[lijeviKontekst][trenutnoStanje].Prioritet < tablicaPrijelaza[lijeviKontekst][indeksStanjaPosljednjegPrihvacenogNiza].Prioritet)
                    {
                        indeksStanjaPosljednjegPrihvacenogNiza = trenutnoStanje;
                    }
                }
            }
        }

        /// <summary>
        /// Metoda koja upisuje leksicku jedinku u tablicu uni. znakova i odgovarajuću tablicu znakova.
        /// </summary>
        /// <param name="imeUniformnogZnaka">ime uniformnog znaka</param>
        private void UpisUniformnogZnaka(string imeUniformnogZnaka)
        {
            StringBuilder izvorniTekst=new StringBuilder();
            for (int i = pocetakNeprepoznatogDijelaNiza; i <= indeksKrajaPosljednjegPrihvacenogNiza; i++)
            {
                izvorniTekst.Append(ulazniZnakovi[i]);
            }
            var izvorniZnak = tablicaIzvornihZnakova.DodajZnak(
                new ZapisTabliceIzvornihZnakova()
                {
                    IzvorniTekst=izvorniTekst.ToString(),
                    UniformniZnak=imeUniformnogZnaka
                });
            listaUniformnihZnakova.DodajUniformniZnak(
                new UniformniZnak()
                {
                    IndeksUTabliciZnakova = izvorniZnak.Indeks,
                    UniformniZnak_=imeUniformnogZnaka,
                    BrojReda = brojReda
                });
        }

        /// <summary>
        /// Dohvaća epsilon okruženje danog skupa stanja
        /// </summary>
        /// <param name="trenutnaStanja">trenutna stanja</param>
        /// <returns>trenutna stanja i sva stanja do kojih se iz
        /// trenutnih stanja može doći epislon prijelazima</returns>
        private ISet<string> epsilonOkruzenje(ISet<string> trenutnaStanja)
        {
            Stack<string> stog = new Stack<string>();

            foreach (var stanje in trenutnaStanja)
            {
                stog.Push(stanje);
            }

            while (stog.Count > 0)
            {
                string trenutnoStanje = stog.Pop();
                if (tablicaPrijelaza[lijeviKontekst][trenutnoStanje].Prijelazi.ContainsKey('€'))
                {
                    foreach (var stanje in tablicaPrijelaza[lijeviKontekst][trenutnoStanje].Prijelazi['€'])
                    {
                        if (!(trenutnaStanja.Contains(stanje)))
                        {
                            trenutnaStanja.Add(stanje);
                            stog.Push(stanje);
                        }
                    }
                }
            }

            return trenutnaStanja;
        }
    }
}
